Skip to content

feat(cache): let a hit rebuild empty folders the task made - #917

Merged
wan9chi merged 2 commits into
feat/cache-rename-targetsfrom
feat/cache-empty-folders
Nov 5, 2026
Merged

wan9chi merged 2 commits into
feat/cache-rename-targetsfrom
feat/cache-empty-folders

Conversation

@wan9chi

@wan9chi wan9chi commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Motivation

A task that makes a folder and leaves it empty can't be rebuilt by a hit today; astro build, for example, makes .astro/collections. fspy records nothing for mkdir, and the archive holds only files, so the folder holding the empty one stays pinned and a clean checkout misses.

Changes

  • fspy records mkdir as a new access kind, Mkdir, before the call. This covers the Unix preload, the Linux seccomp supervisor and Windows NtCreateFile creating a directory; on Windows those calls were recorded as reads.
  • A mkdir'd folder that is empty after the run is rebuildable and a starting point for the climb.
  • The archive gets directory entries for those folders, filtered by output exclusions and ignoreOutput like files: all of them with automatic output tracking, otherwise those an output glob matches. A restore recreates them.
  • Each folder is listed once: a mkdir'd folder above a written path is decided by the climb.

E2E cases: empty_folder_in_output and empty_folder_in_output_globs (synthetic; automatic outputs and explicit output globs) and astro_empty_collections (astro build with Astro 6.4.8, added to packages/tools with a scoped vite override; true clean checkout).

Part of a stack that lets automatic tracking cache tasks which read their own outputs and list the folders holding them, so they also hit on a clean checkout.

🤖 Generated with Claude Code


Note

Medium Risk
Changes core cache fingerprinting, output archiving, and cross-platform fspy semantics; incorrect mkdir or empty-folder handling could cause false cache hits or incomplete restores.

Overview
Cache hits now recreate empty directories the task created (e.g. Astro’s .astro/collections), instead of leaving them missing and forcing misses when a parent folder was deleted.

fspy gains a Mkdir access kind: Unix mkdir / mkdirat (preload + seccomp) and Windows directory NtCreateFile (FILE_CREATE / FILE_OPEN_IF + FILE_DIRECTORY_FILE) are recorded as mkdir, not as reads. Failed mkdirs are still logged, matching rename-target behavior.

Rebuild / fingerprint / archive pipeline: empty task-made folders are treated as rebuildable (skipped in input fingerprints like other output-only trees), collected into empty_folders, written into the output tar.zst as directory entries, and restored on replay. With automatic outputs, all such folders are archived; with explicit output globs, only paths the globs match. Parent folders that hold only rebuildable outputs can be absent on a clean checkout without invalidating the fingerprint.

Adds e2e snapshots for a synthetic empty-folder task (with and without globs) and astro build (Astro 6 in packages/tools).

Reviewed by Cursor Bugbot for commit 5cdc29e. Bugbot is set up for automated code reviews on this repo. Configure here.

@socket-security

socket-security Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​astro@​6.4.897258898100

View full report

@socket-security

socket-security Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Priority Alert  (click "▶" to expand/collapse) Action
Critical priority
Critical CVE: Astro: Remote code execution through AVIF image optimization

CVE: GHSA-26w7-cxv4-gfx2 Astro: Remote code execution through AVIF image optimization (CRITICAL)

Affected versions: < 7.2.8

Patched version: 7.2.8

From: packages/tools/package.json → npm/astro@6.4.8

ℹ Read more on: This package | This alert | What is a critical CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known critical CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/astro@6.4.8. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn

View full report

@wan9chi
wan9chi added this pull request to stack #920 October 30, 2026 03:17
@wan9chi
wan9chi force-pushed the feat/cache-empty-folders branch from d7c8a30 to 78b8ee8 Compare October 30, 2026 03:20
@wan9chi wan9chi added the ci: full Run the slow CI jobs (macOS x64, musl) on this pull request label Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

fspy benchmark

linux

dynamic/launch             change  +0.06%  [ -3.38% ..  +4.64%]  overhead   +56.75%
dynamic/access             change  -1.24%  [ -5.21% ..  +2.46%]  overhead   +19.76%
dynamic/access-relative    change  -0.60%  [ -3.20% ..  +2.97%]  overhead   +68.73%
dynamic/access-contended   change  -1.65%  [ -7.74% ..  +4.93%]  overhead   +47.53%
static/launch              change  +1.56%  [ -1.45% ..  +5.88%]  overhead  +199.82%
static/access              change  -0.09%  [ -1.11% ..  +1.36%]  overhead +1448.40%
static/access-relative     change  -0.36%  [ -1.95% ..  +0.76%]  overhead +1935.48%
static/access-contended    change  +0.66%  [ -3.42% ..  +7.74%]  overhead +1925.80%

macos

dynamic/launch             change  +0.70%  [ -3.53% ..  +5.04%]  overhead  +241.84%
dynamic/access             change  +0.33%  [ -2.11% ..  +2.49%]  overhead    +5.26%
dynamic/access-relative    change  +2.42%  [ -0.49% ..  +6.20%]  overhead  +267.39%
dynamic/access-contended   change  +0.89%  [ -6.95% ..  +7.70%]  overhead    +4.68%

windows

dynamic/launch             change  -0.50%  [ -4.06% ..  +3.08%]  overhead   +27.75%
dynamic/access             change  -0.36%  [ -1.46% ..  +0.75%]  overhead    +1.13%
dynamic/access-relative    change  +0.18%  [ -0.55% ..  +0.73%]  overhead    +1.47%
dynamic/access-contended   change  -0.18%  [ -1.41% ..  +0.89%]  overhead    +1.43%

@wan9chi
wan9chi force-pushed the feat/cache-empty-folders branch from 78b8ee8 to 922a074 Compare October 30, 2026 04:07
@wan9chi wan9chi added ci: full Run the slow CI jobs (macOS x64, musl) on this pull request and removed ci: full Run the slow CI jobs (macOS x64, musl) on this pull request labels Oct 30, 2026
@wan9chi
wan9chi force-pushed the feat/cache-empty-folders branch from 922a074 to 2166c58 Compare October 30, 2026 06:31
@wan9chi
wan9chi force-pushed the feat/cache-empty-folders branch from 2166c58 to b81c7bb Compare October 30, 2026 07:08

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b81c7bb. Configure here.

Comment thread crates/vt/src/session/cache/archive.rs Resolved
@wan9chi
wan9chi force-pushed the feat/cache-empty-folders branch from b81c7bb to e038c4c Compare November 1, 2026 11:55
@wan9chi wan9chi added ci: full Run the slow CI jobs (macOS x64, musl) on this pull request and removed ci: full Run the slow CI jobs (macOS x64, musl) on this pull request labels Nov 1, 2026
@wan9chi wan9chi added the read-write overlap Caching tasks that read and write the same paths label Nov 4, 2026
@wan9chi
wan9chi removed this pull request from stack #920 November 5, 2026 08:52
@wan9chi
wan9chi force-pushed the feat/cache-empty-folders branch from e038c4c to 548a884 Compare November 5, 2026 08:54
@wan9chi
wan9chi added this pull request to stack #942 November 5, 2026 08:55
@wan9chi
wan9chi force-pushed the feat/cache-empty-folders branch from 548a884 to dedf87c Compare November 5, 2026 09:02
Three cases for a task whose output folder holds an empty folder it made.
In `empty_folder_in_output`, the task makes `out/empty`, writes
`out/a.txt` and stats `out`, through `vtt fs`; in
`empty_folder_in_output_globs`, the same task has the explicit `output`
glob `out/**`, which matches `out/empty`. In
`astro_empty_collections`, `astro build` (Astro 6, added to
`packages/tools`) on a one-page site makes `.astro/collections` and leaves
it empty, next to the `.d.ts` files it writes in `.astro`, and stats both
folders.

Today a folder holding an empty folder isn't rebuildable, since fspy
doesn't record a mkdir as one, so removing it, as on a clean checkout, is
a cache miss. On Windows, where a mkdir is recorded as a read, the empty
folder is an input too, and vt names whichever removed input its parallel
validation finds first, so the synthetic cases redact the miss reason.

Astro needs Vite 7, so a scoped `astro>vite` override keeps the global
`vite` override from handing it the Vite+ fork. esbuild and sharp, which
Astro depends on, get their prebuilt binaries as optional dependencies, so
their build scripts are skipped. The Astro case removes `.astro`, `dist`,
`node_modules/.astro` and `node_modules/.vite/deps`, not all of
`node_modules/.vite`, which holds the task cache too, and recreates
nothing, as on a clean checkout. Several inputs are then missing, and vt
names whichever its parallel validation finds first, so the next step
redacts its miss reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A task that makes a folder and leaves it empty, like `astro build` making
`.astro/collections`, pinned the folder holding it as an input: fspy
didn't record a mkdir as one (on Windows it was a read), so the empty
folder wasn't rebuildable, and the archive held only files, so a hit
couldn't recreate it. Removing the folder, as on a clean checkout, was a
cache miss.

fspy now records the path of a mkdir as a mkdir, a new access kind, before
the call, so a failed call, like one finding the folder there already, is
recorded too:

- Unix preload: `mkdir` and `mkdirat`.
- Linux seccomp supervisor, for static binaries: `mkdirat`, plus `mkdir`
  on x86_64.
- Windows: `NtCreateFile` with `FILE_DIRECTORY_FILE` and `FILE_CREATE` or
  `FILE_OPEN_IF`, where `CreateDirectoryW` ends. These opens were recorded
  as reads; a mkdir isn't one on Unix either.

`Rebuildable` takes the mkdirs that are empty folders after the run
(`.DS_Store` aside) as rebuildable folders and climbs from them too, so a
folder holding only such folders, rebuildable folders and files the task
wrote is rebuildable. Each folder is listed once: a mkdir'd folder above a
written path is decided while climbing, where an empty one counts as
rebuildable, and only the other mkdirs are checked for being empty. A run
without inferred reads skips the climb and checks every mkdir, for the
archive.

The archive gets directory entries for the empty folders, filtered by
output exclusions and `ignoreOutput` like files: all of them with
automatic output tracking, and otherwise those an `output` glob matches,
since the glob walk yields files only. Restoring recreates them. A folder
holding anything needs no entry, since what it holds recreates it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wan9chi
wan9chi force-pushed the feat/cache-empty-folders branch from dedf87c to 5cdc29e Compare November 5, 2026 09:17
@wan9chi
wan9chi merged commit 4aa6b1f into main Nov 5, 2026
23 checks passed
@wan9chi
wan9chi deleted the feat/cache-empty-folders branch November 5, 2026 10:44
wan9chi added a commit that referenced this pull request Nov 5, 2026
## Motivation

Follow-ups to the rebuildable-inputs stack (#913–#919, #940, #941), now
merged:

- The real-world case for files renamed into place (#916) used Vitest's
`fsModuleCache`, which renames cache entries rather than outputs.
- #919's e2e cases test a listing entry like any other, so they add no
coverage of their own.
- The stack added eight changelog entries, most of them for what users
see as one feature.
- Socket flagged Astro 6.4.8, which #917 added for its real-world case:
critical advisory GHSA-26w7-cxv4-gfx2 (remote code execution through
AVIF image optimization), fixed in Astro 7.2.8.

## Changes

- New case `next_build_moved_pages`: Next.js 15's `next build` renders
each static page (`index.html`, `404.html`, `500.html`) into
`.next/export` and renames it into `.next/server/pages`. After `.next`
is removed, the next run is a cache hit that restores the pages. The
build log is redacted, since its progress lines, sizes and chunk hashes
vary. It replaces `vitest_fs_module_cache`, along with the SHA-1
redaction only that case used. `next`, `react` and `react-dom` join
`packages/tools`.
- `dist_appears_in_listed_folder` and `oxlint_dist` are removed.
- The changelog entries of #913–#918 are combined into one **Added**
item for rebuildable inputs; #919 (`dist` in listings) and #941 (input
exclusions apply to listings) stay their own short items, and so does
#940's fix.
- On Windows CI, the e2e harness stages fixtures in `RUNNER_TEMP`, on
the checkout's drive: the staged workspaces resolve `node_modules` into
the checkout, and Next.js builds its client entry from a path relative
to the project, which can't cross drives.
- Astro 6.4.8 → 7.3.8, the newest release that still supports the repo's
Node 22.19. Astro 7 needs upstream Vite 8, so its scoped override now
points at Vite 8.3.2. `astro_empty_collections` passes unchanged; its
comment now says Astro 7 stats the project root rather than listing it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Test harness, changelog, and dependency catalog changes only; no
changes to task caching or file-tracking behavior in this diff.
> 
> **Overview**
> Follow-up after the rebuildable-inputs stack lands: **no runtime cache
logic changes**—mostly release notes, snapshot tests, and toolchain
bumps.
> 
> The **changelog** folds the former #913–#918 narrative into one
**Added** “rebuildable inputs” entry and shortens the #919 / #941
**Changed** bullets; #940 stays separate.
> 
> **E2E snapshots** swap coverage: **`next_build_moved_pages`** (Next.js
15 renaming `.next/export` → `.next/server/pages`, cache hit after
deleting `.next`) replaces **`vitest_fs_module_cache`**;
**`dist_appears_in_listed_folder`** and **`oxlint_dist`** are dropped as
redundant with listing behavior elsewhere. Snapshot **redaction** drops
SHA-1 normalization (Vitest-only). On **Windows CI**, fixtures stage
under **`RUNNER_TEMP`** so Next’s client entry paths stay on the
checkout drive.
> 
> **Deps**: catalog adds **Next 15**, **React 19**, bumps **Astro 6 →
7.3.8** (security advisory), and scopes **`astro>vite`** to upstream
**Vite 8.3.2**. The Astro empty-collections fixture comment reflects
Astro 7 **stat**-ing the project root instead of listing it.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
4c8f40c. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci: full Run the slow CI jobs (macOS x64, musl) on this pull request read-write overlap Caching tasks that read and write the same paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant